#include "Backend.h"
#include <d3d12.h>
#include <dxgi1_4.h>
#include "../GLFW.h"

namespace SL
{

struct FrameContext
{
    ID3D12CommandAllocator* CommandAllocator;
    UINT64 FenceValue;
};

void GetHardwareAdapter(IDXGIFactory1* pFactory, IDXGIAdapter1** ppAdapter, bool requestHighPerformanceAdapter = false)
{
    *ppAdapter = nullptr;

    IDXGIAdapter1* adapter = nullptr;

    IDXGIFactory6* factory6 = nullptr;
    if (SUCCEEDED(pFactory->QueryInterface(IID_PPV_ARGS(&factory6))))
    {
        for (UINT adapterIndex = 0; SUCCEEDED(factory6->EnumAdapterByGpuPreference(
                 adapterIndex,
                 requestHighPerformanceAdapter == true ? DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE : DXGI_GPU_PREFERENCE_UNSPECIFIED,
                 IID_PPV_ARGS(&adapter)
             ));
             ++adapterIndex)
        {
            DXGI_ADAPTER_DESC1 desc;
            adapter->GetDesc1(&desc);

            if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
            {
                // Don't select the Basic Render Driver adapter.
                // If you want a software adapter, pass in "/warp" on the command line.
                continue;
            }
            // show memory info
            printf("D_system_mem : %.2f MB\nD_video_mem : %.2f MB\nD_shared_mem : %.2f MB\n",desc.DedicatedSystemMemory/1024.0f/1024,desc.DedicatedVideoMemory/1024.0f/1024, desc.SharedSystemMemory/1024.0f/1024);
            // Check to see whether the adapter supports Direct3D 12, but don't create the
            // actual device yet.
            if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
            {
                break;
            }
        }
    }

    if (adapter == nullptr)
    {
        for (UINT adapterIndex = 0; SUCCEEDED(pFactory->EnumAdapters1(adapterIndex, &adapter)); ++adapterIndex)
        {
            DXGI_ADAPTER_DESC1 desc;
            adapter->GetDesc1(&desc);

            if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
            {
                // Don't select the Basic Render Driver adapter.
                // If you want a software adapter, pass in "/warp" on the command line.
                continue;
            }

            // Check to see whether the adapter supports Direct3D 12, but don't create the
            // actual device yet.
            if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
            {
                break;
            }
        }
    }

    *ppAdapter = adapter;
}

void Backend::Init(AppConfig& config, Window& window)
{
    m_config = config;
    createInstance();
    createDevice();
    createSwapchain(window.getWindow());
    createCommandQueues();
    createFrameResource();
}

bool Backend::createInstance()
{
    UINT dxgiFactoryFlags = 0;

#if defined(_DEBUG)
    // Enable the debug layer (requires the Graphics Tools "optional feature").
    // NOTE: Enabling the debug layer after device creation will invalidate the active device.
    {
        ComPtr<ID3D12Debug> debugController;
        if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
        {
            debugController->EnableDebugLayer();

            // Enable additional debug layers.
            dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
        }
    }
#endif

    return CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&m_context.factory)) != S_OK;
}

bool Backend::createSwapchain(GLFWwindow* window)
{
    // create swapchain
    DXGI_SWAP_CHAIN_DESC1 sd;
    {
        ZeroMemory(&sd, sizeof(sd));
        sd.BufferCount = m_config.backBufferNum;
        sd.Width = m_config.windowDesc.width;
        sd.Height = m_config.windowDesc.height;
        sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        sd.Flags = DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
        sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
        sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
        sd.Scaling = DXGI_SCALING_STRETCH;
        sd.Stereo = FALSE;
    }
    IDXGISwapChain1* swapChain1;
    if (m_context.factory->CreateSwapChainForHwnd(
            m_context.graphicsQueue, glfwGetWin32Window(window), &sd, nullptr, nullptr, &swapChain1
        ) != S_OK)
    {
        return false;
    }
    if (swapChain1->QueryInterface(IID_PPV_ARGS(&m_context.swapChain)) != S_OK)
        return false;

    // create render target view (RTV)
    // create depth stencil view (DSV)
}

bool Backend::createDevice()
{
    IDXGIAdapter1* hardwareAdapter;
    GetHardwareAdapter(m_context.factory, &hardwareAdapter, true);
    D3D12CreateDevice(hardwareAdapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_context.device));
}

bool Backend::createCommandQueues()
{
    // create command queue for graphics, compute, copy, (bundle)
    D3D12_COMMAND_QUEUE_DESC queueDesc = {};
    queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    if (m_context.device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_context.graphicsQueue)) != S_OK)
        return false;

    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE;
    if (m_context.device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_context.computeQueue)) != S_OK)
        return false;

    // create command allocator for each frame resource
    if (m_context.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_context.commandAllocator)) != S_OK)
        return false;

    // create command list for each frame resource
    for (UINT i = 0; i < m_config.framesInFlight; i++)
        if (m_context.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_frameContext[i].CommandAllocator)) !=
            S_OK)
            return false;
}

ID3D12GraphicsCommandList* Backend::createCommandList()
{
    ID3D12GraphicsCommandList* cmdlist;
    if (m_context.device->CreateCommandList(
            0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_frameContext[0].CommandAllocator, nullptr, IID_PPV_ARGS(&cmdlist)
        ) != S_OK ||
        cmdlist->Close() != S_OK)
        return nullptr;
    return cmdlist;
}

Backend::~Backend(){

}

void Backend::Shutdown()
{
    // release all resources
    // release device
    // release factory
    this->~Backend();
}

void Backend::PreRender()
{
    // wait for next frame resource
    // reset command allocator
    // reset command list
}

void Backend::PostRender()
{
    // execute command list
    // present swapchain
}

} // namespace SL